Delegated Setup
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 411 of xUnit Test Patterns for the latest information.
How do we construct the Fresh Fixture?
Each test creates its own Fresh Fixture by calling Creation Methods from within the Test Methods.
Sketch Delegated Setup embedded from Delegated Setup.gif
To execute an automated test, we require a text fixture that is well understood and completely deterministic. We are using a Fresh Fixture (page X) approach to build a Minimal Fixture (page X) for the use of this one test and we'd like to avoid Test Code Duplication (page X).
Delegated Setup lets us reuse the code to set up the fixture without compromising our goal of Tests as Documentation (see Goals of Test Automation on page X).
How It Works
Each Test Method (page X) sets up it's own test fixture by calling one or more Creation Methods (page X) to construct exactly the test fixture it requires. To ensure Tests as Documentation, we build a Minimal Fixture using Creation Methods that build fully-formed objects ready for use by the test. We strive to ensure that the method calls will convey the "big picture" to the test reader by only passing in the values that impact the behavior of the system under test (SUT).
When To Use It
We can use a Delegated Setup when we want to avoid the Test Code Duplication caused by having to set up similar fixtures for several tests and we want to keep the nature of the fixture visible within the Test Methods. A reasonable goal is to encapsulate the essential but irrelevant steps of setting up the fixture and leave only the steps and values essential to understand the test within the Test Method. This helps us achieve Tests as Documentation by ensuring that excess Inline Setup (page X) code does not obscure the intent of the test. But it also avoids Mystery Guest (see Obscure Test on page X) by leaving the Intent Revealing Name[SBPP] of the Creation Method call within the Test Method.
Furthermore, it allows us to use whatever organization scheme we want for our Test Methods; we are not forced to put Test Methods that require the same test fixture into the same Testcase Class (page X) just to reuse the setUp method as we would have to when using Implicit Setup (page X). Furthermore, it helps prevent Fragile Tests (page X) by moving much of the non-essential interaction with the SUT out of the very numerous Test Methods and into a much smaller number of Creation Method bodies where it is easier to maintain.
Implementation Notes
With modern refactoring tools, the first cut of a Creation Method can often be created with a simple Extract Method[Fowler] refactoring. As we are writing a set of tests using "clone and twiddle", we must watch for any Test Code Duplication in the fixture setup logic within our tests. For each object that needs to be verified in the verification logic, we extract a Creation Method that takes only those attributes as parameters that affect the outcome of the test(s).
Initially, we can leave the Creation Method on our Testcase Class but if we need to share them with another class, we can move them to a Testcase Superclass (page X) or a Test Helper (page X) class.
Motivating Example
Suppose we are testing the state-model of the Flight class. In each test, we need to have a flight in the right state. But a flight needs to connect at least two airports. So we need to create airports before we can create a flight. Of course, airports are typically associated with cities or states/provinces. But to keep the example manageable, our airports only require a city name and an airport code.
public void testStatus_initial() { // inline setup: Airport departureAirport = new Airport("Calgary", "YYC"); Airport destinationAirport = new Airport("Toronto", "YYZ"); Flight flight = new Flight(flightNumber, departureAirport, destinationAirport); // Exercise SUT & verify outcome assertEquals(FlightState.PROPOSED, flight.getStatus()); // tearDown: // garbage-collected } public void testStatus_cancelled() { // inline setup: Airport departureAirport = new Airport("Calgary", "YYC"); Airport destinationAirport = new Airport("Toronto", "YYZ"); Flight flight = new Flight( flightNumber, departureAirport, destinationAirport); flight.cancel(); // still part of setup // Exercise SUT & verify outcome assertEquals(FlightState.CANCELLED, flight.getStatus()); // tearDown: // garbage-collected } Example InlineSetup embedded from java/com/clrstream/ex6/services/test/SetupStyles.java
Refactoring Notes
We can refactor the fixture setup logic by using Extract Method refactoring to remove any frequently repeated code sequences into utility methods with Intent Revealing Names but we leave the calls to the methods in the test so that the reader can see what is being done. The utility method bodies contain the irrelevant mechanics of carrying out the intent. The method calls that remain within the test will convey the "big picture" to the test reader. If we need to share the Delegated Setups with another Testcase Class, we can use either a Pull Up Method[Fowler] refactoring to move them to a Testcase Superclass or a Move Method[Fowler] refactoring to move them to a Test Helper class.
Example: Delegated Setup
In this version of the test we have chosen to use a method that hides the fact that we need two airports instead of creating the two airports needed by the flight in line within each Test Method. We could get to this version of the tests either through refactoring or by writing the test in this intent revealing style right off the bat.
public void testGetStatus_inital() { // setup: Flight flight = createAnonymousFlight(); // Exercise SUT & verify outcome: assertEquals(FlightState.PROPOSED, flight.getStatus()); // tearDown: // garbage-collected } public void testGetStatus_cancelled2() { // setup: Flight flight = createAnonymousCancelledFlight(); // Exercise SUT & verify outcome: assertEquals(FlightState.CANCELLED, flight.getStatus()); // tearDown: // garbage-collected } Example DelegatedSetup embedded from java/com/clrstream/ex6/services/test/SetupStyles.java
The simplicity of these tests was made possible by the following Creation Methods that hide the "necessary but irrelevant" steps from the test reader:
private int uniqueFlightNumber = 2000; public Flight createAnonymousFlight(){ Airport departureAirport = new Airport("Calgary", "YYC"); Airport destinationAirport = new Airport("Toronto", "YYZ"); Flight flight = new Flight( new BigDecimal(uniqueFlightNumber++), departureAirport, destinationAirport); return flight; } public Flight createAnonymousCancelledFlight(){ Flight flight = createAnonymousFlight(); flight.cancel(); return flight; } Example DelegatedSetupUtilities embedded from java/com/clrstream/ex6/services/test/SetupStyles.java
Copyright © 2003-2008 Gerard Meszaros all rights reserved